﻿using System;
using FStore.Utils;
using System.Reflection;
using FStore.Errors;
using FStore.Formats;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace FStore.Formats
{
    /// <summary>
    /// Allows an instance to be stored in differing formats depending on the property being stored
    /// </summary>
    /// <typeparam name="KFormat">The default format to use if no overrides are specified</typeparam>
    public sealed class DecoratedSerFormat<KFormat> : IFlatFormat where KFormat : IFlatFormat
    {

        public string Decompose<T>(T obj)
        {
            // Param checking
            ErrorHelper.GenInvaildParam("obj", obj, obj != null, "Cannot decompose a null instance");
            ErrorHelper.GenInvaildParam("obj", obj, Attribute.IsDefined(obj.GetType(), typeof(FlatDecoratedSerAttribute)),
                "Object isn't decorated with FlatDecoratedSerAttribute");

            // Make sure the default format isn't a format we can't use
            ErrorHelper.GenInvaildParam("[KFormat]", typeof(KFormat).AssemblyQualifiedName,
                typeof(KFormat).IsGenericType == false, "KFormat mustn't be nested formats, or generic format");

            // Get the type represented by T
            Type _t = typeof(T);

            // Get all the properties that aren't marked as "Hidden" to the serialiser
            PropertyInfo[] props = _t.GetProperties().Where(property =>
                Attribute.IsDefined(property, typeof(FlatDecoratedSerHiddenAttribute)) == false).ToArray();

            // Get the global format
            IFlatFormat globalFormat = Activator.CreateInstance<KFormat>();

            string strRet = typeof(KFormat).AssemblyQualifiedName + "\t";

            Func<PropertyInfo, string> doGlobal = (pi) => { return pi.Name + "\t" + globalFormat.Decompose<serWrapper>(new serWrapper() { Value = pi.GetValue(obj, null), }) + "\t"; };
            foreach (PropertyInfo pi in props)
            {
                if (Attribute.IsDefined(pi, typeof(FlatDecoratedSerFormatOverride)))
                {
                    // Format override
                    Type formatType =
                        ((FlatDecoratedSerFormatOverride)
                        Attribute.GetCustomAttribute(pi, typeof(FlatDecoratedSerFormatOverride)))._type;

                    if (formatType == null || formatType == globalFormat.GetType())
                    {
                        strRet += doGlobal(pi);
                    }
                    else
                    {
                        strRet += formatType.AssemblyQualifiedName + "&" + pi.Name + "\t" +
                            ((IFlatFormat)Activator.CreateInstance(formatType)).Decompose<serWrapper>(
                            new serWrapper()
                            {
                                Value = pi.GetValue(obj, null),
                            });
                    }
                }
                else
                {
                    strRet += doGlobal(pi);
                }
            }

            return strRet.TrimEnd('\t');
        }


        public string Decompose(Type type, object obj)
        {
            ErrorHelper.GenInvaildParam("obj", obj, obj != null, "Cannot decompose a null instance");
            ErrorHelper.GenInvaildParam("obj", obj, Attribute.IsDefined(obj.GetType(), typeof(FlatDecoratedSerAttribute)),
                "Object isn't decorated with FlatDecoratedSerAttribute");

            Type _t = type;

            PropertyInfo[] props = _t.GetProperties().Where(property =>
                Attribute.IsDefined(property, typeof(FlatDecoratedSerHiddenAttribute)) == false).ToArray();

            IFlatFormat globalFormat = Activator.CreateInstance<KFormat>();

            string strRet = typeof(KFormat).AssemblyQualifiedName + "\t";

            Func<PropertyInfo, string> doGlobal = (pi) => { return pi.Name + "\t" + globalFormat.Decompose<serWrapper>(new serWrapper() { Value = pi.GetValue(obj, null), }) + "\t"; };
            foreach (PropertyInfo pi in props)
            {
                if (Attribute.IsDefined(pi, typeof(FlatDecoratedSerFormatOverride)))
                {
                    // Format override
                    Type formatType =
                        ((FlatDecoratedSerFormatOverride)
                        Attribute.GetCustomAttribute(pi, typeof(FlatDecoratedSerFormatOverride)))._type;

                    if (formatType == null || formatType == globalFormat.GetType())
                    {
                        strRet += doGlobal(pi);
                    }
                    else
                    {
                        strRet += formatType.AssemblyQualifiedName + "&" + pi.Name + "\t" +
                            ((IFlatFormat)Activator.CreateInstance(formatType)).Decompose<serWrapper>(
                            new serWrapper()
                            {
                                Value = pi.GetValue(obj,null),
                            });
                    }
                }
                else
                {
                    strRet += doGlobal(pi);
                }
            }

            return strRet.TrimEnd('\t');
        }

        [Serializable()]
        [System.Xml.Serialization.XmlRoot(ElementName = "w")]
        public sealed class serWrapper
        {
            [System.Xml.Serialization.XmlElement(ElementName = "v")]
            public object Value { get; set; }
        }

        public T Compose<T>(string str)
        {
            ErrorHelper.GenInvaildParam<string>("str", str, string.IsNullOrEmpty(str) == false,
                "Cannot compose from an empty or null string");

            ErrorHelper.GenInvaildParam<string>("str", str, str.Contains('\t'),
                "Cannot compose from string, it isn't of a valid format");

            string[] breakdown = str.Split('\t');

            Type globalFromString = Type.GetType(breakdown[0], false);

            ErrorHelper.GenError(globalFromString != null, "Failed to compose from string [{0}], the type [{1}] couldn't be loaded".UFormat(str, breakdown[0]));

            ErrorHelper.GenError(globalFromString.GetInterface(
                typeof(IFlatFormat).FullName) != null,
                "Failed to compose from string [{0}], the type [{1}] isn't an IFlatFormat");

            IFlatFormat globalFormatter = (IFlatFormat)Activator.CreateInstance(globalFromString);

            Dictionary<string, Tuple<IFlatFormat, string, object>> mappings = new Dictionary<string, Tuple<IFlatFormat, string, object>>();

            //bool bExpectingValue = false;
            string nameForValue = null;

            // Go through the other items
            for (int i = 1; i < breakdown.Length; i++)
            {
                string cItem = breakdown[i];

                if (cItem.Contains('&'))    // We have an override
                {
                    string[] overrideDetails = cItem.Split('&');

                    Type t = Type.GetType(overrideDetails[0],false);

                    ErrorHelper.GenError(t != null,
                        "Failed to load Formatter type [{0}]".UFormat(overrideDetails[0]));

                    ErrorHelper.GenError(t.GetInterface(
                        typeof(IFlatFormat).FullName) != null,
                        "Failed to load formatter type [{0}], it isn't an IFlatFormatter".UFormat(overrideDetails[0]));

                    mappings.Add(overrideDetails[1], new Tuple<IFlatFormat, string, object>(
                        (IFlatFormat)Activator.CreateInstance(t), null, null));

                    nameForValue = overrideDetails[1];
                    string k = string.Empty;

                }
                else
                {
                    if (nameForValue == null)
                    {
                        nameForValue = cItem;
                        mappings.Add(cItem, new Tuple<IFlatFormat, string, object>(globalFormatter, null, null));
                    }
                    else
                    {
                        mappings[nameForValue] = new Tuple<IFlatFormat, string, object>(
                            mappings[nameForValue].Item1, cItem, null);
                        nameForValue = null;
                    }
                }
            }

            for (int i = 0; i < mappings.Count; i++)
            {
                var ent = mappings.ElementAt(i);
                var tup = ent.Value;

                mappings[ent.Key] = new Tuple<IFlatFormat, string, object>(
                    tup.Item1, null,
                    tup.Item1.Compose<serWrapper>(tup.Item2).Value);
            }

            T inst = Activator.CreateInstance<T>();

            PropertyInfo[] props = typeof(T).GetProperties().Where(
                property => Attribute.IsDefined(property, typeof(FlatDecoratedSerHiddenAttribute)) == false).ToArray();

            foreach (PropertyInfo pi in props)
            {
                pi.SetValue(inst, mappings[pi.Name].Item3, null);
            }

            return inst;
        }

        public object Compose(Type type, string str)
        {
            ErrorHelper.GenInvaildParam<string>("str", str, string.IsNullOrEmpty(str) == false,
                "Cannot compose from an empty or null string");

            ErrorHelper.GenInvaildParam<string>("str", str, str.Contains('\t'),
                "Cannot compose from string, it isn't of a valid format");

            string[] breakdown = str.Split('\t');

            Type globalFromString = Type.GetType(breakdown[0], false);

            ErrorHelper.GenError(globalFromString != null, "Failed to compose from string [{0}], the type [{1}] couldn't be loaded".UFormat(str, breakdown[0]));

            ErrorHelper.GenError(globalFromString.GetInterface(
                typeof(IFlatFormat).FullName) != null,
                "Failed to compose from string [{0}], the type [{1}] isn't an IFlatFormat");

            IFlatFormat globalFormatter = (IFlatFormat)Activator.CreateInstance(globalFromString);

            Dictionary<string, Tuple<IFlatFormat, string, object>> mappings = new Dictionary<string, Tuple<IFlatFormat, string, object>>();

            //bool bExpectingValue = false;
            string nameForValue = null;

            // Go through the other items
            for (int i = 1; i < breakdown.Length; i++)
            {
                string cItem = breakdown[i];

                if (cItem.Contains('&'))    // We have an override
                {
                    string[] overrideDetails = cItem.Split('&');

                    Type t = Type.GetType(overrideDetails[0], false);

                    ErrorHelper.GenError(t != null,
                        "Failed to load Formatter type [{0}]".UFormat(overrideDetails[0]));

                    ErrorHelper.GenError(t.GetInterface(
                        typeof(IFlatFormat).FullName) != null,
                        "Failed to load formatter type [{0}], it isn't an IFlatFormatter".UFormat(overrideDetails[0]));

                    mappings.Add(overrideDetails[1], new Tuple<IFlatFormat, string, object>(
                        (IFlatFormat)Activator.CreateInstance(t), null, null));

                    nameForValue = overrideDetails[1];
                    string k = string.Empty;

                }
                else
                {
                    if (nameForValue == null)
                    {
                        nameForValue = cItem;
                        mappings.Add(cItem, new Tuple<IFlatFormat, string, object>(globalFormatter, null, null));
                    }
                    else
                    {
                        mappings[nameForValue] = new Tuple<IFlatFormat, string, object>(
                            mappings[nameForValue].Item1, cItem, null);
                        nameForValue = null;
                    }
                }
            }

            for (int i = 0; i < mappings.Count; i++)
            {
                var ent = mappings.ElementAt(i);
                var tup = ent.Value;

                mappings[ent.Key] = new Tuple<IFlatFormat, string, object>(
                    tup.Item1, null,
                    tup.Item1.Compose<serWrapper>(tup.Item2).Value);
            }

            object inst = Activator.CreateInstance(type);

            PropertyInfo[] props = type.GetProperties().Where(
                property => Attribute.IsDefined(property, typeof(FlatDecoratedSerHiddenAttribute)) == false).ToArray();

            foreach (PropertyInfo pi in props)
            {
                pi.SetValue(inst, mappings[pi.Name].Item3, null);
            }

            return inst;
        }

    }


    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    public sealed class FlatDecoratedSerAttribute : Attribute
    {
        public FlatDecoratedSerAttribute() { }

    }

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
    public sealed class FlatDecoratedSerHiddenAttribute : Attribute { }

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
    public sealed class FlatDecoratedSerFormatOverride : Attribute
    {
        internal Type _type = null;

        public FlatDecoratedSerFormatOverride(Type formatType)
        {
            _type = formatType;
        }
    }

    public static class FlatDecoratedSerHelper
    {
        public static IEnumerable<Type> GetTypesOnDecoratedClass<T>(Type type) where T : IFlatFormat
        {
            ErrorHelper.GenInvaildParam<Type>("type", type, type != null, "Type cannot be null");
            ErrorHelper.GenInvaildParam("type", type, Attribute.IsDefined(type,
                typeof(FlatDecoratedSerAttribute)), "Type isn't decorated with FlatDecoratedSerAttribute");

            yield return typeof(T);

            List<Type> _had = new List<Type>();

            foreach (PropertyInfo pi in type.GetProperties().Where(
                property => Attribute.IsDefined(property, typeof(FlatDecoratedSerHiddenAttribute)) == false))
            {
                if (Attribute.IsDefined(pi, typeof(FlatDecoratedSerFormatOverride)))
                {
                    Type ty = ((FlatDecoratedSerFormatOverride)Attribute.GetCustomAttribute(pi, typeof(FlatDecoratedSerFormatOverride)))._type;
                    if (ty != null)
                    {
                        if (_had.Contains(ty) == false && ty != typeof(T))
                        {
                            _had.Add(ty);
                            yield return ty;
                        }
                    }
                }
            }
        }

        public static string ReduceToIndexes(string decomposed, Dictionary<string, string> mappings)
        {
            string strRet = decomposed;

            foreach (KeyValuePair<string, string> map in mappings.Distinct())
            {
                strRet = Regex.Replace(strRet, map.Key, map.Value);
            }

            return strRet;
        }

        public static string DecompAndReduce<TFormat, TType>(TType obj, Dictionary<Type, string> map) where TFormat : IFlatFormat
        {
            IEnumerable<Type> typeList = GetTypesOnDecoratedClass<TFormat>(typeof(TType));
            string decomp = new DecoratedSerFormat<TFormat>().Decompose<TType>(obj);
            Dictionary<string, string> nMap = new Dictionary<string, string>();
            foreach (var mapping in map)
            {
                nMap.Add(mapping.Key.AssemblyQualifiedName, mapping.Value);
            }
            string repr = ReduceToIndexes(decomp, nMap);

            return repr;
        }

        public static string InflateFromIndexes(string decomposed, Dictionary<string, Type> map)
        {
            string strRet = decomposed;

            foreach (var item in map.Distinct())
            {
                strRet = Regex.Replace(strRet, item.Key, item.Value.AssemblyQualifiedName);
            }

            return strRet;
        }

        public static TType InflateAndCompose<TFormat, TType>(string strData, Dictionary<string, Type> map) where TFormat : IFlatFormat
        {
            string strRep = strData;

            foreach (var mapping in map.Distinct())
            {
                strRep = Regex.Replace(strRep, mapping.Key, mapping.Value.AssemblyQualifiedName);
            }

            TType comp = new DecoratedSerFormat<TFormat>().Compose<TType>(strRep);

            return comp;
        }
    }
}

namespace FStore
{
    [FlatDecoratedSerAttribute]
    public class TestDecorate
    {
        public string Name { get; set; }

        [FlatDecoratedSerFormatOverride(typeof(BinarySerFormat))]
        public string Email { get; set; }

        [FlatDecoratedSerHidden]
        public string SessionID { get; set; }
    }

}